﻿using System;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
using System.Text;
using System.Xml;

namespace MadLibrary.Text
{
    /// <summary>
    /// Provides System.String extension methods.
    /// </summary>
    public static class StringExtensions
    {
        /// <summary>
        /// Determines if the given string is a number (i.e. All characters are digits).
        /// </summary>
        /// <param name="this">The string to check.</param>
        /// <returns>True if all characters are digits, false otherwise.</returns>
        public static bool IsNumeric(this string @this)
        {
            return @this.All(char.IsDigit);
        }

        /// <summary>Returns the extracted digits.</summary>
        /// <param name="value">The value.</param>
        /// <returns>.</returns>
        public static string GetDigits(this string value)
        {
            return value.Where(Char.IsDigit).Aggregate(new StringBuilder(value.Length), (sb, c) => sb.Append(c)).ToString();
        }

        /// <summary>
        /// Formats the value with the parameters using string.Format. (A shortcut extension)
        /// </summary>
        /// <param name="value">The input string.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns>The formatted value.</returns>
        public static string Format(this string value, params object[] parameters)
        {
            return string.Format(value, parameters);
        }

        /// <summary>Converts the string to title case.</summary>
        /// <param name="value">The value.</param>
        /// <returns>value as a string.</returns>
        public static string ToTitleCase(this string value)
        {
            return CultureInfo.CurrentUICulture.TextInfo.ToTitleCase(value);
        }

        /// <summary>
        /// Encodes the string to Base64. [Default encoding is UTF8].
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns>value as a string.</returns>
        public static string ToBase64(this string value)
        {
            return value.ToBase64(null);
        }

        /// <summary>
        /// Encodes the string to Base64.
        /// <para>[Default encoding is UTF8]</para>
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="encoding">The encoding.</param>
        /// <returns></returns>
        public static string ToBase64(this string value, Encoding encoding)
        {
            return Convert.ToBase64String((encoding ?? Encoding.UTF8).GetBytes(value));
        }

        /// <summary>Decodes the Base 64 encoded value to string. [Default encoding is UTF8].</summary>
        /// <param name="value">The value.</param>
        /// <returns>.</returns>
        public static string DecodeFromBase64(this string value)
        {
            return value.DecodeFromBase64(null);
        }

        /// <summary>
        /// Decodes the Base 64 encoded value to string.
        /// <para>[Default encoding is UTF8]</para>
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="encoding">The encoding.</param>
        /// <returns></returns>
        public static string DecodeFromBase64(this string value, Encoding encoding)
        {
            return (encoding ?? Encoding.UTF8).GetString(Convert.FromBase64String(value));
        }

        /// <summary>Converts the string into <see cref="Guid"/>.</summary>
        /// <param name="value">The value.</param>
        /// <returns>The <see cref="Guid"/>.</returns>
        public static Guid ToGuid(this string value)
        {
            return new Guid(value);
        }

        /// <summary>
        /// Convert the string to a <see cref="Guid"/> and returns the provided default value if the
        /// conversion fails.
        /// </summary>
        /// <param name="value">The original string value.</param>
        /// <param name="defaultvalue">The defaultvalue.</param>
        /// <returns>The <see cref="Guid"/>.</returns>
        public static Guid ToGuid(this string value, Guid defaultvalue)
        {
            if(string.IsNullOrWhiteSpace(value))
                return defaultvalue;

            try
            {
                return value.ToGuid();
            }
            catch { }
            return defaultvalue;
        }

        /// <summary>
        /// A string extension method that converts this object to a <see cref="SecureString"/>.
        /// </summary>
        /// <param name="value">The value to act on.</param>
        /// <param name="readOnly">(optional) the read only.</param>
        /// <returns>The given data converted to a <see cref="SecureString"/>.</returns>
        public static SecureString ToSecureString(this string value, bool readOnly = true)
        {
            SecureString ss = new SecureString();

            foreach(char c in value) { ss.AppendChar(c); }
            if(readOnly) ss.MakeReadOnly();

            return ss;
        }

        /// <summary>
        /// A SecureString extension method that converts the <see cref="SecureString"/> to an un-secure string.
        /// </summary>
        /// <param name="ss">The <see cref="SecureString"/> to act on.</param>
        /// <returns><see cref="SecureString"/> as a string.</returns>
        public static string ToUnSecureString(this SecureString ss)
        {
            IntPtr bstr = Marshal.SecureStringToBSTR(ss);

            try
            {
                return Marshal.PtrToStringBSTR(bstr);
            }
            finally
            {
                Marshal.FreeBSTR(bstr);
            }
        }

        /// <summary>
        /// Appends the given text formatted with the given arguments to the specified <see cref="System.Text.StringBuilder"/>.
        /// </summary>
        /// <param name="this">The StringBuilder to append the text to.</param>
        /// <param name="format">The string to format.</param>
        /// <param name="args">The arguments to format the string with.</param>
        public static void AppendFormat(this StringBuilder @this, string format, params object[] args)
        {
            @this.Append(format.Format(args));
        }

        /// <summary>A byte extension method that gets a string.</summary>
        /// <param name="data">The buffer to act on.</param>
        /// <param name="encoding">(optional) the second parameter.</param>
        /// <returns>The string.</returns>
        public static byte[] GetBytes(this string data, Encoding encoding = null)
        {
            return (encoding ?? Encoding.UTF8).GetBytes(data);
        }

        /// <summary>A byte extension method that gets a string.</summary>
        /// <param name="buffer">The buffer to act on.</param>
        /// <param name="encoding">(optional) the second parameter.</param>
        /// <returns>The string.</returns>
        public static string GetString(this byte[] buffer, Encoding encoding = null)
        {
            return (encoding ?? Encoding.UTF8).GetString(buffer);
        }

        /// <summary>Truncates.</summary>
        /// <param name="value">The text.</param>
        /// <param name="count">The maximum characters.</param>
        /// <returns>.</returns>
        public static string Truncate(this string value, int count)
        {
            if(string.IsNullOrEmpty(value))
                return value;
            if(value.Length <= count)
                return value;
            return value.Substring(0, count);
        }

        /// <summary>
        /// Tabulators.
        /// </summary>
        /// <param name="s">The s.</param>
        /// <param name="dept">The dept.</param>
        /// <returns>
        /// .
        /// </returns>
        public static string Tabulate(this string s, int dept)
        {
            StringBuilder sb = new StringBuilder(s);
            for(int i = 1; i <= dept; i++)
            {
                sb.Append("  ");
            }
            return sb.ToString();
        }

        /// <summary>
        /// Computes the hash.
        /// </summary>
        /// <param name="data">The data.</param>
        /// <param name="encoding">The encoding.</param>
        /// <returns></returns>
        public static string ComputeMD5(this string data, Encoding encoding = null)
        {
            byte[] buffer = data.GetBytes(encoding);
            byte[] hash = null;
            using(HashAlgorithm ha = MD5.Create())
            {
                ha.Initialize();
                hash = ha.ComputeHash(buffer, 0, buffer.Length);
            }
            StringBuilder hex = new StringBuilder(data.Length * 2);
            foreach(byte b in hash) hex.AppendFormat("{0:x2}", b);
            return hex.ToString();
        }
    }
}